/*
 * Copyright 2009 Fredrik Bridell  
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); 
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */

package com.bridell.punity;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import processing.core.PApplet;
import processing.core.PConstants;
import processing.core.PFont;

/** 
 * The Tester is the class that manages the testing, instantiate and perform tests
 * on a Tester from your Processing app. 
 */
public class Tester {

	// instance variables 
	private PApplet papplet; // the Processing applet
	private List<TestResult> results; // for storing test results 
	private PFont font; 
	
	/* used from Processing to see if we're testing */
	private boolean testing = false; 
	
	/**
	 * The counter is not used to count all test, but to count the tests
	 * since setting a label. This is useful to identify test results.
	 * For example, calling setLabel("export") and then calling a bunch
	 * of assert methods will create test results with IDs export#1, export#2 etc,
	 */
	private int counter = 1;
	private String label = "";
	
	/** constructor 
	 * @param  parent  the Processing Applet creating this test 
	 */
	public Tester(PApplet parent){
		papplet = parent;
		results = new ArrayList<TestResult>();
		
		// TODO improve font handling, test on different platforms
		// is there a way to distribute data (a .vlw file, say) in
		// the library (and not just in an example?) 
		font = papplet.createFont("Arial", 24);
		reset();
	}
	
	/**
	 * Asserts that an actual value is equal to a value you expect. The result
	 * of the test is stored.
	 * @param expected  the value you expect
	 * @param actual    the actual value
	 * @param message   Anything you want to add to remember what you are testing
	 * @return          true, if the values are equal, else false
	 */
	public boolean assertEquals(Comparable<?> expected, Comparable<?> actual, String message){
		TestResult r = new TestResult((actual == expected), TestType.EQUALS, expected, actual, label, message, counter++);
		results.add(r);
		return r.passed();
	}
	
	/**
	 * Asserts that an actual value is equal to a value you expect. The result 
	 * of the test is stored. 
	 * @param expected  the value to test against
	 * @param actual    the actual value
	 * @return          true, if the values are NOT equal, else false
	 */
	public boolean assertEquals(Comparable<?> expected, Comparable<?> actual){
		return assertEquals(expected, actual, label);
	}
	
	/**
	 * Asserts that an actual value is NOT equal to some value. 
	 * @param expected  the value to test against
	 * @param actual    the actual value
	 * @param message   Anything you want to add to identify this test
	 * @return          true, if the values are NOT equal, else false
	 */
	public boolean assertNotEquals(Comparable<?> expected, Comparable<?> actual, String message){
		TestResult r = new TestResult((actual != expected), TestType.NOTEQUALS, expected, actual, label, message, counter);
		return r.passed();
	}
	
	/** 
	 * Forces a failed test.
	 * @param  message  any message that helps you identify this test 
	 */
	public boolean fail(String message){
		TestResult r = new TestResult(false, TestType.FAIL, null, null, label, message, counter++); 
		return r.passed();
	}
	
	/**
	 *  Forces a passed test 
	 * @param  message  any message that helps you identify this test 
	 */
	public boolean pass(String message){
		TestResult r = new TestResult(true, TestType.PASS, null, null, label, message, counter++);
		return r.passed();
	}
	
	/**
	 * Shows the result in the Processing applet. If all tests passed
	 * it will show a green square, else a red one. 
	 */
	public void showSummary(){
		if (countFailures()==0){
			papplet.fill(64, 192, 64); // green
		} else {
			papplet.fill(192, 64, 64); // red
			printFailures();
		}
		papplet.rect(10, 10, papplet.width-20, papplet.height-20);
		papplet.textFont(font, 24);
		papplet.textAlign(PConstants.CENTER);
		papplet.fill(0);
		papplet.text("Performed " + countTests() + 
				" tests, " + countFailures() + " failed.", papplet.width/2, 
				papplet.height/2);
	}

	/**
	 * prints the failures to the console.
	 */
	public void printFailures() {
		for (Iterator<TestResult> iterator = results.iterator(); iterator.hasNext();) {
			TestResult r = iterator.next();
			if (!r.passed()){
				PApplet.println(r.toString());
			}
		}
	}

	/** 
	 * Prints all test results to the console.
	 */
	public void printResults(){
		PApplet.println("Punity Test Results: ");
		PApplet.println("---");
		for (Iterator<TestResult> iterator = results.iterator(); iterator.hasNext();) {
			TestResult r = iterator.next();
			PApplet.println(r.toString());
		}
		PApplet.println("---");
	}
	
	
	/**
	 * Counts how many test results there are. 
	 * @return  the number of tests since the last call to clearResults
	 */
	private int countTests() {
		return results.size();
	}
	
	
	/** 
	 * Resets the Tester for another set of tests. This will remove all 
	 * test results, reset the counter and reset the current label. 
	 */
	public void reset(){
		counter=1;
		label="";
		results.clear();
	}
	
	/**
	 * Counts how many of the performed tests that failed. 
	 * @return  the number of failed tests
	 */
	public int countFailures(){
		int failures=0;
		for (Iterator<TestResult> iterator = results.iterator(); iterator.hasNext();) {
			TestResult r = iterator.next();
			if (!r.passed()){
				failures++;
			}
		}
		return failures;
	}
	
	/** 
	 * Sets the label used to identify test results. Setting a label will 
	 * reset the counter to 1. 
	 * @param  label  any String you like to use to identify your results 
	 */
	public void setLabel(String label){
		this.label = label;
		counter = 1;
	}
	
	/** 
	 * Sets the "testing" bit. This is really just a boolean that
	 * is handy to have in your Processing sketch, and it's here just
	 * to save you from needing to declare a variable yourself.
	 * @param  testing  true, if the Tester should be seen as active, else false
	 */
	public void setTesting(boolean testing){
		this.testing = testing;
	}
	
	/**
	 * Checks the value of the "testing" bit. This can be handy in the 
	 * the Processing sketch. Typically you test this value in the draw method
	 * and if you are testing you display the results, otherwise carry on whatever
	 * you would normally do. Note that the testing bit is not set automatically,
	 * you will need to call setTesting manually from the Processing sketch.
	 * 
	 * @return  true, if the Tester is 
	 */
	public boolean isTesting(){
		return testing;
	}
}
